/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo
*/

#include "simodo/interpret/Interpret.h"
#include "simodo/interpret/AnalyzeException.h"

#include "simodo/bormental/DrBormental.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/format/fmt.h"

#include <algorithm>
#include <cassert>

#define SemanticFiber_DEBUG_no

namespace simodo::interpret
{
    loom::FiberStatus Interpret::start()
    {
        InterpretState state = InterpretState::Flow;

        for(auto h : _hosts) {
            if (h->before_start() != InterpretState::Flow) {
                state = InterpretState::Complete;
            }
        }

        return state;
    }

    loom::FiberStatus Interpret::tieKnot()
    {
        _waiting_for = nullptr;
        assert(!_errors);

        if (_layer_stack.empty())
            return loom::FiberStatus::Complete;

        _layer_stack.back().index = flow().internal_work_index;

        assert(flow().index < flow().code.branches().size());

        _layer_stack.back().internal_work_index += 1;

        const ast::Node & node = flow().code.branches()[flow().index];

        if (checkBreakPoint(node))
            return loom::FiberStatus::Paused;

#ifndef SemanticFiber_DEBUG
        try
        {
#endif
            /// \todo Оптимизация на случай большого кол-ва семантических модулей :)
            /// В данном случае можно было бы упростить

            const std::u16string & current_host_name = node.host();

            if (_last_host_name != current_host_name) {
                auto it_host = std::find_if(_hosts.begin(), _hosts.end(), 
                                [current_host_name](SemanticModule_interface *h)
                                { 
                                    return h->checkSemanticName(current_host_name);
                                });

                if (it_host == _hosts.end()) {
                    std::string semantics_name = inout::toU8(current_host_name);
                    if (semantics_name.empty())
                        semantics_name = "SBL";
                    throw bormental::DrBormental("interpret::Interpret::tieKnot",
                                    inout::fmt("Владелец семантики '%1' не определён")
                                    .arg(semantics_name));
                }

                _last_host_name = current_host_name;
                _last_host      = *it_host;
            }

            _last_host->performOperation(node);

            while(true) {
                if (flow().internal_work_index < flow().code.branches().size())
                    break;
                    
                FlowLayerInfo flow_info = flow();

                _layer_stack.pop_back();

                if (flow_info.on_layer_end) {
                    /// \todo Нужно добавить обработку возвращаемого значения, чтобы можно было управлять выполнением
                    /// \todo Нужно убедиться, что в нештатных ситуациях, но при продолжении выполнения, вызов функции будет произведён
                    flow_info.on_layer_end(flow_info);
                }

                if (_layer_stack.empty())
                    break;
            }

#ifndef SemanticFiber_DEBUG
        }
        catch (const AnalyzeException & e)
        {
            _m.reportError(e.location(), e.what());
            _errors = true;
            return loom::FiberStatus::Complete;
        }
        catch (const bormental::DrBormental & e)
        {
            _m.reportError( node.token().makeLocation(files()),
                            inout::fmt("Произошло исключение в методе %1 при обработке оператора %2\nОписание исключения: %3")
                            .arg(e.where())
                            .arg(makeOperatorString(node))                        
                            .arg(e.what()));
            _errors = true;
            return _stop_by_error ? loom::FiberStatus::Paused : loom::FiberStatus::Complete;
        }
        catch (const std::exception & e)
        {
            _m.reportError( node.token().makeLocation(files()),
                            inout::fmt("Произошло исключение при обработке оператора %1\nОписание исключения: %2" )
                            .arg(makeOperatorString(node))
                            .arg(e.what()));
            _errors = true;
            return _stop_by_error ? loom::FiberStatus::Paused : loom::FiberStatus::Complete;
        }
        catch (...)
        {
            _m.reportError(node.token().makeLocation(files()),
                            inout::fmt("Произошло исключение при обработке оператора %1")
                            .arg(makeOperatorString(node)));
            _errors = true;
            return _stop_by_error ? loom::FiberStatus::Paused : loom::FiberStatus::Complete;
        }
#endif
        if (_waiting_for)
            return loom::FiberStatus::Delayed;

        return _layer_stack.empty() ? loom::FiberStatus::Complete : loom::FiberStatus::Flow;
    }

    void Interpret::finish()
    {
        for(auto h : _hosts) {
            if (h->before_finish(InterpretState::Flow) != InterpretState::Flow)
                _errors = true;
        }
    }

    bool Interpret::isReady() const
    {
        return !_layer_stack.empty();
    }

    const loom::Fiber_interface * Interpret::getWaitingFor()
    {
        return _waiting_for;
    }

    std::u16string Interpret::makeOperatorString(const ast::Node & operation_node)
    {
        return (operation_node.host().empty()) ? u"SBL" : operation_node.host() + 
                                            u"(" + inout::toU16(std::to_string(static_cast<int>(operation_node.operation()))) + 
                                            u", '" + operation_node.token().lexeme() + u"')";
    }

    bool Interpret::checkBreakPoint(const ast::Node & node)
    {
        /// \todo Нужно исключить повторные проверки для одной и той же строки (и файла), которые бывают часто

        inout::uri_index_t      uri_index   = node.token().location().uri_index();
        inout::position_line_t  line        = node.token().location().range().start().line()+1;

        if (uri_index >= _files.size())
            return false;

        const std::string & uri = _files[uri_index];

        auto it = std::find_if(_breakpoints.begin(), _breakpoints.end(),
                    [uri, line](const BreakPoint & bp){
                        return uri.find(bp.uri) != std::string::npos && bp.line == line;
                    });

        if (it == _breakpoints.end())
            return false;

        _breakpoints.erase(it);
            
        return true;
    }

}